from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from hello_selenium.scrapy_settings_config import ScrapySettingsConfig

# https://www.selenium.dev/


class SeleniumMiddleware:
    def __init__(self, config: ScrapySettingsConfig):
        self.config = config

        options = webdriver.EdgeOptions()
        options.add_argument("--headless=new")
        options.add_argument("--no-sandbox")

        if config.use_remote_browser:
            # Connect to the Selenium server running in the selenium/standalone-edge | selenium/standalone-chrome container.
            self.driver = webdriver.Remote(
                command_executor=f"{self.config.selenium_remote_host}/wd/hub",
                options=options
            )
        else:
            # This is for working with local browser.
            service = webdriver.EdgeService()
            self.driver = webdriver.Edge(service=service, options=options)

        self.timeout = 20
        self.logged_in = False

    @classmethod
    def from_crawler(cls, crawler):
        # Instantiate config class with all required settings
        config = ScrapySettingsConfig(crawler)

        # This method is called to create the middleware instance
        s = cls(config)
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)

        return s

    def spider_opened(self, spider):
        # Any initialization code if needed
        spider.driver = self.driver

    def spider_closed(self, spider):
        # Clean up WebDriver
        self.driver.quit()

    def process_request(self, request, spider):
        """
        If need you can add [use_selenium] to meta to check if need to use seleinum.
        """

        if not self.logged_in:
            self._login()

        self.driver.get(request.url)
        wait_selector = request.meta.get("wait_selector")
        click_selector = request.meta.get("click_selector")

        # Wait for the specified element if provided
        if wait_selector:
            WebDriverWait(self.driver, timeout=self.timeout).until(
                EC.presence_of_all_elements_located(
                    (By.CSS_SELECTOR, wait_selector))
            )

        # Click the specified element if provided
        if click_selector:
            element = WebDriverWait(self.driver, timeout=self.timeout).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, click_selector))
            )

            element.click()

        return HtmlResponse(
            url=request.url,
            body=self.driver.page_source,
            encoding="utf-8",
            request=request,
        )

    def _clear_login_data(self):
        """
        IMPORTANT!!!
        When system uses token based authentication the related data are stored on client.
        System might be designed NOT to access login page if is already authenticated.
        That makes your login process cannot locate the elements.
        That's why we need to clear login data.
        """

        self.driver.delete_all_cookies()
        self.driver.execute_script("window.localStorage.clear();")
        self.driver.execute_script("window.sessionStorage.clear();")

    def _login(self):
        """
        Login to system to make other api requests.
        """

        self.driver.get(
            f"{self.config.remote_host}/authentication/login/")

        try:
            # Wait for the username input to be present
            WebDriverWait(self.driver, timeout=5).until(
                EC.presence_of_element_located(
                    (By.CSS_SELECTOR, "input.el-input__inner[type='text']"))
            ).send_keys("13900000000")
        except TimeoutException:
            # If system redircts user after user logged in then the element will be NOT found.
            # If you want to re-login then you nned to clear login data first.
            self._clear_login_data()
            self.driver.get(
                f"{self.config.remote_host}/authentication/login/")

            WebDriverWait(self.driver, timeout=self.timeout).until(
                EC.presence_of_element_located(
                    (By.CSS_SELECTOR, "input.el-input__inner[type='text']"))
            ).send_keys("13900000000")

            # Here we don't need to re-login so we just change the status and return.
            # self.logged_in = True

            # return

        # Wait for the password input to be present
        WebDriverWait(self.driver, timeout=self.timeout).until(
            EC.presence_of_element_located(
                (By.CSS_SELECTOR, "input.el-input__inner[type='password']"))
        ).send_keys("qqqqqq")

        # Wait for the login button to be present
        WebDriverWait(self.driver, timeout=self.timeout).until(
            EC.presence_of_element_located(
                (By.CSS_SELECTOR, "button.el-button--primary"))
        ).click()

        # If url changes after login then check on it and wait for login completes.
        # IMPORTANT!!!
        # When running vue3 project under development the behavior might NOT is your expected. Proceed with caution.
        #
        # WebDriverWait(self.driver, self.timeout).until(
        #     EC.url_changes(self.driver.current_url)
        # )

        # If login form dispears after login then check on it and wait for login completes.
        # IMPORTANT!!!
        # When running vue3 project under development the behavior might NOT is your expected. Proceed with caution.
        #
        # WebDriverWait(self.driver, self.timeout).until(
        #     EC.invisibility_of_element_located(
        #         (By.CSS_SELECTOR, "button.el-button--primary"))
        # )

        # If other elements show up after login then check on it and wait for login completes.
        WebDriverWait(self.driver, self.timeout).until(
            EC.presence_of_element_located(
                (By.CSS_SELECTOR, "div.header-user span.el-avatar"))
        )

        self.logged_in = True
